package pl.ydp.components.mathcell.mathrecognizer.geometry.optimizer
{
	import flash.geom.Point;
	
	import pl.ydp.components.mathcell.mathrecognizer.Debug;
	import pl.ydp.components.mathcell.mathrecognizer.engine.Functions;
	import pl.ydp.components.mathcell.mathrecognizer.engine.Parameters;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.Angle;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.AngleTrend;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.EuclideanVector;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.Primitive;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.VectorDirection;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.classification.PrimitiveType;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.PrimitiveExtractor;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.PrimitiveExtractorTask;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.features.FeaturesLoop;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.extractor.features.LoopDiameter;
	
	public class PrimitivesOptimizer
	{
		
		public static function optimize(primitives:Array):Array{
			
			primitives = jointPrimitives(primitives);
			
			primitives = mergeLoops(primitives);
			
			primitives = abosrbPrimitives(primitives);
			
			primitives = collectJunks(primitives);
			
			primitives = removeDeadPrimitives(primitives);
			
			return primitives;
			
		}
		
		//------------------------------------------------------------------
		
		private static function jointPrimitives(primitives:Array):Array{
			
			
			Debug.saveLog(new Array(), "JOINT");
			
			for (var p:Number = 0 ; p < 6 ; p ++){
				if (p < primitives.length){
					Debug.saveLog(primitives[p].vectors, "Primitive "+p.toString()+" (before joints) type: "+primitives[p].type.toString()+"    in private function jointPrimitives()");
				} else {
					Debug.saveLog(new Array , "Primitive 'none'    in private function jointPrimitives()");
					Debug.saveLog(new Array , "Primitive 'none'    in private function jointPrimitives()");
				}
			}
			
			
			var jointCandidates:Array = new Array();
			var jointProcessed:Boolean = false;
			var doJoint:Boolean;
			
			var d1:Number, d2:Number;
			var refAngle:Number;
			var testAngle:Number;
			var curveLineEndingsDistances:Array;
			var refProcessingBegin:Boolean;
			var testProcessingBegin:Boolean;
			var refPoint:Point;
			var v:Number;
			var vStart:Number;
			var vStop:Number;
			var vStep:Number;
			
			var JOINT_CURVE_LINE_LENGTH_RATIO:Number = 0.5;
			
			for (var ref:Number = 0 ; ref < primitives.length ; ref ++){
				
				if (primitives[ref].type == PrimitiveType.LOOP  ||  primitives[ref].type == PrimitiveType.UNKNOWN){
					continue;
				}
				
				for (var test:Number = 0 ; test < primitives.length ; test ++){
					
					if (test == ref  ||  primitives[test].type == PrimitiveType.LOOP  ||  primitives[test].type == PrimitiveType.UNKNOWN){
						continue;
					}	
					
					doJoint = false;
					refProcessingBegin = false;
					testProcessingBegin = true;
					
					//  --- FIND GENERAL JOINT CAPACITY ---
					
					// CHECK TWO LINES
					
					if (PrimitiveType.isLine(primitives[ref].type)  &&  PrimitiveType.isLine(primitives[test].type)){
						
						if (!PrimitiveType.areOrthogonal(primitives[ref].type, primitives[test].type)  &&
							Angle.differenceAbs(primitives[ref].angle, primitives[test].angle) < Parameters.MAX_JOINT_LINE_ANGLE_DIFF
							  &&  Point.distance(primitives[test].begin,primitives[ref].begin) > Point.distance(primitives[test].begin,primitives[ref].end)){
								
							doJoint = true;	
							refAngle = primitives[ref].angle;
							testAngle = primitives[test].angle;
							
						}
						
					}	
					
					// CHECK TWO CURVES
					
					if (PrimitiveType.isCurve(primitives[ref].type)  &&  PrimitiveType.isCurve(primitives[test].type)){
						
						var minDistance:Number = Infinity;
						var minDistanceTestIndex:Number;
						var endIsInTestBody:Boolean = false;
						var stepFound:Boolean;
						
						var vs:Number;
						
						for (v = 0 ; v < primitives[test].vectors.length ; v += Math.round(Parameters.JOINT_DISTANCE_MAX_VECTORS_COUNT/2)){
							if (Point.distance(primitives[ref].end, primitives[test].vectors[v].point) < minDistance){
								minDistance = Point.distance(primitives[ref].end, primitives[test].vectors[v].point);
								minDistanceTestIndex = v;
							}
						}
						
						if (minDistance < Parameters.MAX_JOINT_DISTANCE){
							
							for (v = minDistanceTestIndex ; v >= 0 ; v -= Parameters.JOINT_DISTANCE_MAX_VECTORS_COUNT){
								
								stepFound = false;
								
								for (vs = primitives[ref].vectors.length-1 ; vs > 0 ; vs -- ){
									if (Point.distance(primitives[test].vectors[v].point, primitives[ref].vectors[vs].point) < Parameters.MAX_JOINT_DISTANCE){
										stepFound = true;
										vs -= Parameters.JOINT_DISTANCE_MAX_VECTORS_COUNT
										break;		
									}
								}
							}
							
							if (stepFound){
								doJoint = true;
								refAngle = primitives[ref].angleEnd;
							}
							
						}  //  end if (endIsInTestBody
						
						
					}
					
					
					// CHECK REF = CURVE, TEST + LINE
					
					if (PrimitiveType.isCurve(primitives[ref].type)  &&  PrimitiveType.isLine(primitives[test].type)){
						
						curveLineEndingsDistances = new Array;
						
						curveLineEndingsDistances.push( Point.distance(primitives[ref].begin, primitives[test].begin) );
						curveLineEndingsDistances.push( Point.distance(primitives[ref].begin, primitives[test].end) );
						curveLineEndingsDistances.push( Point.distance(primitives[ref].end, primitives[test].begin) );
						curveLineEndingsDistances.push( Point.distance(primitives[ref].end, primitives[test].end) );
						
						var minDistanceIndex:Number = Functions.findMinimumIndex(curveLineEndingsDistances);
						
						switch (minDistanceIndex){
							case (0):
							refProcessingBegin = true;
							testProcessingBegin = true;
							break;
							case (1):
							refProcessingBegin = true;
							testProcessingBegin = false;
							break;
							case (2):
							refProcessingBegin = false;
							testProcessingBegin = true;
							break;
							case (3):
							refProcessingBegin = false;
							testProcessingBegin = false;
							break;
						}
							
						if (refProcessingBegin){
							refAngle = primitives[ref].angleBegin;
						} else {
							refAngle = primitives[ref].angleEnd;
						}
						
						if (PrimitiveType.isWave(primitives[test].type)){
							
							if (testProcessingBegin){
								testAngle = primitives[test].angleBegin;
							} else {
								testAngle = primitives[test].angleEnd;
							}
							
						} else {
							testAngle = primitives[test].angle;	
						}
						
						
						if ((refProcessingBegin == testProcessingBegin  &&  
								Angle.differenceAbsUnified(refAngle, testAngle) < Parameters.MAX_JOINT_LINE_ANGLE_DIFF  &&  
								Angle.differenceAbs(refAngle, testAngle) > AngleTrend.ANGLE_HALF - Parameters.MAX_JOINT_LINE_ANGLE_DIFF )  ||
							(refProcessingBegin != testProcessingBegin  &&  Angle.differenceAbs(refAngle, testAngle) < Parameters.MAX_JOINT_LINE_ANGLE_DIFF ) ){
							if (primitives[ref].length  >  JOINT_CURVE_LINE_LENGTH_RATIO*primitives[test].length){
								doJoint = true;
							}
						}

						
					} // end if curve + line
					
					if (!doJoint){
						continue;
					}
					
					if (testProcessingBegin){
						vStart = 0;
						vStop = primitives[test].vectors.length - 3;
						vStep = 1;
					} else {
						vStart = primitives[test].vectors.length - 1;
						vStop = 2;
						vStep = -1;
					}
					
					if (refProcessingBegin){
						refPoint = primitives[ref].begin;
					} else {
						refPoint = primitives[ref].end;		
					}
					
					
					for (v = vStart ; v != vStop ; v += vStep){
						
						var angleDiff:Number = Angle.difference(primitives[ref].angleEnd, primitives[test].vectors[v].angle);
					
						var angleDiffAbs:Number = Math.abs( Angle.difference(primitives[ref].angleEnd, primitives[test].vectors[v].angle) );
						
						var angleDiffUnifiedAbs:Number = Angle.differenceAbsUnified(refAngle, primitives[test].vectors[v].angle);
						
						var dist:Number = Point.distance(primitives[ref].end, primitives[test].vectors[v].point);
						
						if( 
							(
								(
									(PrimitiveType.isLine(primitives[ref].type)  &&  PrimitiveType.isLine(primitives[test].type))  
									&&
									angleDiffAbs < Parameters.MAX_JOINT_LINE_ANGLE_JOINT_DIFF
								) 
								||
								(	
									PrimitiveType.isCurve(primitives[ref].type)  &&  PrimitiveType.isCurve(primitives[test].type)  
									&&  
									angleDiffAbs < Parameters.MAX_JOINT_CURVE_ANGLE_JOINT_DIFF
									&&
									angleDiff > Parameters.MAX_JOINT_CURVE_ANGLE_JOINT_DIFF_NEGATIVE
									
								)
								||
								(	
									PrimitiveType.isCurve(primitives[ref].type)  &&  PrimitiveType.isLine(primitives[test].type)  
									&&  
									angleDiffUnifiedAbs < Parameters.MAX_JOINT_CURVE_ANGLE_JOINT_DIFF
									
								)
							)  
							&&
							Point.distance(refPoint, primitives[test].vectors[v].point) < Parameters.MAX_JOINT_DISTANCE
						  ) // if ()
						{
							// if TEST primitive is extension of REF primitive 
							
							var vectorToJoint:Array;
							
							if (testProcessingBegin){
								vectorToJoint = primitives[test].vectors.slice(v);	
							} else {
								vectorToJoint = primitives[test].vectors.slice(0,v+1);
							}
							
							if (!refProcessingBegin  &&  testProcessingBegin){
								
								primitives[ref].add(vectorToJoint);
								
							} else { 
								
								primitives[ref].add(vectorToJoint, 
													refProcessingBegin,
													refProcessingBegin  ==  testProcessingBegin
													);
							}
							
							primitives[test].reset();
							jointProcessed = true;
							break;
							
						} 
						
					} // end for v
					
				} // end for test
			} // end for ref					
			
			if (jointProcessed){
				//primitives = removeDeadPrimitives(primitives);
			}
			
			for (p = 0 ; p < primitives.length ; p ++){
				Debug.saveLog(primitives[p].vectors, "Primitive "+p.toString()+" (after joints) type: "+primitives[p].type.toString()+"    in private function jointPrimitives()");	
			}
			
			return primitives;
			
		}
		
		//------------------------------------------------------------------
		
		private static function abosrbPrimitives(primitives:Array):Array{
			
			var refSize:Number;
			var testSize:Number;
			var distance:Number;
			
			for (var ref:Number = 0 ; ref < primitives.length ; ref ++){
				for (var test:Number = 0 ; test < primitives.length ; test ++){
					
					if (ref == test  ||  primitives[ref].type == PrimitiveType.NONE){
						continue;
					}
					
					// ABSORB DOTS
					
					if (primitives[ref].type == PrimitiveType.DOT  &&  primitives[test].type == PrimitiveType.DOT){
										
						refSize = Point.distance(primitives[ref].field.topLeft, primitives[ref].field.bottomRight);
						
						testSize = Point.distance(primitives[test].field.topLeft, primitives[test].field.bottomRight);
						
						distance = Point.distance(primitives[ref].cog, primitives[test].cog);
						
						if (distance < Parameters.ABSORBTION_DOT_MAX_DISTANCE  ||  distance < refSize  ||  distance < testSize){
						}
												
					} // end if (primitives[ref] == DOT
					
					
					// ABSORB LOOPS
					
					if (primitives[ref].type == PrimitiveType.LOOP  &&  primitives[test].type == PrimitiveType.LOOP){
						
						refSize = Point.distance(primitives[ref].field.topLeft, primitives[ref].field.bottomRight);
						
						testSize = Point.distance(primitives[test].field.topLeft, primitives[test].field.bottomRight);
						
						distance = Point.distance(primitives[ref].cog, primitives[test].cog);
						
						if (distance < Parameters.ABSORBTION_LOOP_COG_MAX_DISTANCE  &&
							(refSize/testSize < Parameters.ABSORBTION_LOOP_SIZE_RATIO  ||  testSize/refSize < Parameters.ABSORBTION_LOOP_SIZE_RATIO)){
							
							if (refSize > testSize){
								primitives[test].reset();
							} else {
								primitives[ref].reset();
							}
						}
						
					} // end if LOOP
					
					
					// ABSORB LINES
					
					
					if ((primitives[ref].type & PrimitiveType.LINE)  &&  (primitives[test].type & PrimitiveType.LINE)){
					
						if (Angle.differenceAbsUnified(primitives[ref].angle, primitives[ref].angle) < Parameters.ABSORBTION_LINE_ANGLE_DIFF){
							
							//distance
							
							var euv:EuclideanVector;
							
							var passedBegin:Boolean = false;
							var passedEnd:Boolean = false;
							
							for each (euv in primitives[ref].vectors){
								if (Point.distance(euv.point, primitives[test].begin) < Parameters.ABSORBTION_LINE_DISTANCE_MAX){
									passedBegin = true;
									break;
								}
							}
							
							for each (euv in primitives[ref].vectors){
								if (Point.distance(euv.point, primitives[test].end) < Parameters.ABSORBTION_LINE_DISTANCE_MAX){
									passedEnd = true;
									break;
								}
							}

							if (passedBegin  &&  passedEnd){
								primitives[test].reset();
							}
							
						}
						
					}
					
					
					// ABSORB CURVES
					
					
					if (((primitives[ref].type & PrimitiveType.LOOP)  ||  (primitives[ref].type & PrimitiveType.CURVE))  &&  
						((primitives[test].type & PrimitiveType.CURVE)  ||  (primitives[test].type & PrimitiveType.LINE))){
						
						var v:Number;
						var test_v:Number;
						
						var stepFound:Boolean;
						
						var beginIndex:Number = -1;
						var endIndex:Number = -1;
						
						var CRAWL_STEP:Number = Math.round(Parameters.MIN_LINE_LENGTH);
						
						// step 1 - compare directions (by comparing primitive curve type)
						if ((primitives[ref].type & PrimitiveType.LOOP)  ||
								(
								(primitives[ref].type & PrimitiveType.CURVE)  
								&&
									(
									(primitives[ref].type & primitives[test].type) == primitives[test].type  // curve
									||
									VectorDirection.isBetween(primitives[test].direction, primitives[ref].directionBegin, primitives[ref].directionEnd)  // line
									)
								)
							){
							
							
							for (v = 0 ; v < primitives[ref].vectors.length ; v ++ ){
								if (Point.distance(primitives[test].begin, primitives[ref].vectors[v].point) < Parameters.MAX_JOINT_DISTANCE){
									beginIndex = v;
									break;
								}
							}
							
							for (v = primitives[ref].vectors.length-1 ; v > 0 ; v -- ){
								if (Point.distance(primitives[test].end, primitives[ref].vectors[v].point) < Parameters.MAX_JOINT_DISTANCE){
									endIndex = v;
									break;
								}
							}
							
							// step 2 - TEST's begin & end are close to REF
							if (beginIndex != -1  &&  endIndex != -1){
								
								v = beginIndex;
								
								stepFound = true;
								
								for (test_v = CRAWL_STEP ; test_v < primitives[test].vectors.length ; test_v += CRAWL_STEP ){
									
									if (!stepFound){
										break;
									}
										
									stepFound = false;
									
									if (beginIndex <= endIndex){
										for ( ; v < endIndex  ; v ++  ){
											if (Point.distance(primitives[test].vectors[test_v].point, primitives[ref].vectors[v].point) < Parameters.MAX_JOINT_DISTANCE){
												stepFound = true;
												v += CRAWL_STEP;
												break;
											}
										}
									} else {
										if (v < primitives[ref].vectors.length  &&  v >= beginIndex){
											for ( ; v < primitives[ref].vectors.length  ; v ++ ){
												if (Point.distance(primitives[test].vectors[test_v].point, primitives[ref].vectors[v].point) < Parameters.MAX_JOINT_DISTANCE){
													stepFound = true;
													v += CRAWL_STEP;
													break;
												}
											}
											if (v >= primitives[ref].vectors.length){
												stepFound = true;
												v = 0;
											}
										} else {
											for ( ; v < endIndex  ; v ++ ){
												if (Point.distance(primitives[test].vectors[test_v].point, primitives[ref].vectors[v].point) < Parameters.MAX_JOINT_DISTANCE){
													stepFound = true;
													v += CRAWL_STEP;
													break;
												}
											}
										
											
										}
									}
									
									
								}
								
								if (stepFound){
									primitives[test].reset();
								}
								
							} 
							
						}  // end if step 1
						
						
					}  // end if (CURVE && CURVE  ||  LOOP && CURVE)
										
				}  // end for (test...
				
			}  // end for (ref...
			
			return primitives;
			
		}
		
		//------------------------------------------------------------------

		
		private static function mergeLoops(primitives:Array):Array{
			
			var ref:Number;
			var test:Number;
			
			var distance:Number;
			var diameter:LoopDiameter;
			var v:Number;
			
			for (ref = 0 ; ref < primitives.length ; ref ++){
				if (!(primitives[ref].type & PrimitiveType.CURVE)){
					continue;
				}
				
				distance = Point.distance(primitives[ref].begin, primitives[ref].end);
				
				diameter = FeaturesLoop.findDiameter(primitives[ref].vectors);
				
				if (distance < Parameters.LOOP_MAX_CLOSURE_DISTANCE  && diameter.length > Parameters.LOOP_MIN_DIAMETER  &&  primitives[ref].length > Parameters.LOOP_MIN_LENGTH){
					
					var loopExtractor:PrimitiveExtractor = new PrimitiveExtractor(primitives[ref].vectors, PrimitiveExtractorTask.LOOPS);
					
					if (loopExtractor.substances.length > 0){
						primitives[ref].type = PrimitiveType.LOOP;
					}
					/*
					var maxDiameter:Number = 0;
					
					for (test = 0 ; test < primitives[ref].vectors.length ; test ++ ){
						
						distance = Point.distance(primitives[ref].vectors[0].point, primitives[ref].vectors[test].point);
						
						if (distance > maxDiameter){
							maxDiameter = distance;
						}
					}
					
					if (maxDiameter > Parameters.LOOP_MIN_DIAMETER){
						primitives[ref].type = PrimitiveType.LOOP;
					}
					*/
					
				}
			}
			
			// MERGE CURVES and LINES into LOOPS
			
			for (ref = 0 ; ref < primitives.length ; ref ++){
				
				if (!(primitives[ref].type & PrimitiveType.CURVE)){
					continue;
				}
				
				for (test = 0 ; test < primitives.length ; test ++){
					
					if (ref == test  ||  
						!( (primitives[test].type & PrimitiveType.LINE)  ||  (primitives[test].type & PrimitiveType.CURVE) )  ||  
						primitives[ref].length <  primitives[test].length){
						continue;
					}
					
					var dbb:Number = Point.distance(primitives[ref].begin, primitives[test].begin);
					var dee:Number = Point.distance(primitives[ref].end, primitives[test].end);
					var dbe:Number = Point.distance(primitives[ref].begin, primitives[test].end);
					var deb:Number = Point.distance(primitives[ref].end, primitives[test].begin);
					var dmin:Number = Math.min(dbb, dee, dbe, deb);
					var dToCheck1:Number = dmin;
					var dToCheck2:Number = Infinity;
					if (dmin == dbb)
						dToCheck2 = dee;
					else if (dmin == dee)
						dToCheck2 = dbb;
					else if (dmin == dbe)
						dToCheck2 = deb;
					else if (dmin == deb)
						dToCheck2 = dbe;
					var dmax:Number = Parameters.MAX_JOINT_DISTANCE;
					var MAX_DISTANCE:Number = Parameters.LOOP_MAX_CLOSURE_DISTANCE;
					
					if (dToCheck1 < MAX_DISTANCE  &&  dToCheck2 < MAX_DISTANCE){
						
						// find maxDiameter
						
						var approved:Boolean = false;
						
						for (v = 0 ; v < primitives[ref].vectors.length ; v ++ ){
							
							distance = Point.distance(primitives[ref].vectors[v].point, primitives[test].begin);
							
							if (distance > Parameters.LOOP_MIN_DIAMETER){
								approved = true;
								diameter = new LoopDiameter(primitives[test].begin, primitives[ref].vectors[v].point);
								break;
							}
							
						}// end for (v...
						
						if (approved){
							
							var realCenter:Point = new Point;
							
							realCenter.x = (primitives[ref].cogReal.x * primitives[ref].length + primitives[test].cogReal.x * primitives[test].length)/
											(primitives[ref].length + primitives[test].length);
							realCenter.y = (primitives[ref].cogReal.y * primitives[ref].length + primitives[test].cogReal.y * primitives[test].length)/
											(primitives[ref].length + primitives[test].length);
														
							diameter = FeaturesLoop.findDiameterSecond2V(primitives[test].vectors, primitives[ref].vectors, realCenter);
							
							if (diameter.length > Parameters.LOOP_DIAMETER_SECOND_MIN){
								approved = true;
							} else {
								approved = false;
							}
						}
						
						
						if (approved){
							if (Point.distance(primitives[ref].begin, primitives[test].begin) < MAX_DISTANCE  &&  
								Point.distance(primitives[ref].end, primitives[test].end) < MAX_DISTANCE){
								
								primitives[ref].add(primitives[test].vectors, false, true);
																
							} else {
								
								primitives[ref].add(primitives[test].vectors, false, false);
								
							}
							
							primitives[test].reset();
						}
					}
					
				}  // end for (test...
				
			}  // end for (ref...
			
			return primitives;
			
		}
		
		//------------------------------------------------------------------

		
		private static function collectJunks(primitives:Array):Array{
			
			var test:Number;
			
			for each (var currPrim:Primitive in primitives){
				
				if (PrimitiveType.isLine(currPrim.type)){
					
					if (currPrim.vectors.length < Parameters.JUNKS_LINE_LENGTH_MIN){
						currPrim.reset();
					}
					
				}
				
				if (PrimitiveType.isCurve(currPrim.type)){
					
					if (currPrim.vectors.length < Parameters.JUNKS_CURVE_LENGTH_MIN){
						currPrim.reset();
					}
					
				}
				
			}
			
			return primitives;
			
		}
		
		//------------------------------------------------------------------
		
		private static function removeDeadPrimitives(primitives:Array):Array{
			var primitivesJointed:Array = new Array;
			
			for each (var currPrim:Primitive in primitives){
				if (currPrim.type != PrimitiveType.NONE){
					primitivesJointed.push(currPrim);
				}
			}
			
			return primitivesJointed;
			
		}
		
		
		//------------------------------------------------------------------
	}
}